package engine.game.gfx;

import engine.entities.Entity;
import engine.entities.Player;
import engine.game.Game;
import engine.game.level.Level;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JPanel;

public class Screen extends JPanel {

    private static final long serialVersionUID = 1L;
    
    private static final Game game = Game.getGlobalGame();

    public int screenWidth;
    public int screenHeight;
    public int SCALE;

    public int[] colors;

    public int xOffset = 0;
    public int yOffset = 0;

    public int pixelsPerGrid = 16;

    private BufferedImage imageToDraw;
    private BufferedImage bufferImage;
    private int[] bufferPixels;

    private Point hTile;

    private Player centerPlayer;
    private Level level;

    private boolean selectMode = false;
    private ScreenSelectFilter selFil;
    private ScreenSelectProcessor selProc;

    private ScreenListener screenList = new ScreenListener();
    
    private Entity prevViewed;

    public Screen(int width, int height, int scale, int[] colors, Player centerPlayer, Level level) {
        this.SCALE = scale;
        this.colors = colors;
        this.centerPlayer = centerPlayer;
        this.level = level;

        imageToDraw = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        bufferImage = new BufferedImage(width * scale, height * scale, BufferedImage.TYPE_INT_RGB);
        bufferPixels = ((DataBufferInt) bufferImage.getRaster().getDataBuffer()).getData();

        this.addMouseListener(screenList);
        this.addMouseMotionListener(screenList);
    }

    public void render(int xPos, int yPos, Renderable render, int scale) {
        xPos -= xOffset;
        yPos -= yOffset;

        int[][] renderPixels = render.pixels();
        int renderWidth = renderPixels.length;
        int renderHeight = renderPixels[0].length;
        for (int i = 0; i < renderWidth; i++) {
            for (int j = 0; j < renderHeight; j++) {
                int col = renderPixels[i][j];
                if (col < 255) {
                    for (int yScale = 0; yScale < scale; yScale++) {
                        if (yPos + j + yScale < 0 || yPos + j + yScale >= screenHeight) {
                            continue;
                        }
                        for (int xScale = 0; xScale < scale; xScale++) {
                            if (xPos + i + xScale < 0 || xPos + i + xScale >= screenWidth) {
                                continue;
                            }
                            bufferPixels[(xPos + i + xScale) + (yPos + j + yScale) * screenWidth] = colors[col];
                        }
                    }
                }
            }
        }
    }

    public void render(Level level) {
        this.screenWidth = getWidth() / SCALE;
        this.screenHeight = getHeight() / SCALE;

        if (screenWidth <= 0 || screenHeight <= 0) {
            return;
        }

        xOffset = centerPlayer.xOff() + centerPlayer.getCoords().x * level.TILE_SIZE - (screenWidth / 2);
        yOffset = centerPlayer.yOff() + centerPlayer.getCoords().y * level.TILE_SIZE - (screenHeight / 2);

        if (!(screenWidth == bufferImage.getWidth() && screenHeight == bufferImage.getHeight())) {
            recreateImages();
        }

        bufferPixels = ((DataBufferInt) bufferImage.getRaster().getDataBuffer()).getData();
        level.render(this, xOffset, yOffset);

        if (hTile != null) {
            int leftX = hTile.x * level.TILE_SIZE;
            int rightX = (hTile.x + 1) * level.TILE_SIZE;
            int topY = hTile.y * level.TILE_SIZE;
            int bottomY = (hTile.y + 1) * level.TILE_SIZE;
            for (int i = leftX; i < rightX; i++) {
                for (int j = topY; j < bottomY; j++) {
                    if (i == leftX || i == (rightX - 1) || j == topY || j == (bottomY - 1)) {
                        bufferPixels[i + (j * screenWidth)] = 0xFFFFFF;
                    }
                }
            }
        }

        BufferedImage tempImage = imageToDraw;
        imageToDraw = bufferImage;
        bufferImage = tempImage;

        repaint();
    }

    @Override
    public void paintComponent(Graphics g) {
        Graphics2D g2D = (Graphics2D) g.create();
        g2D.drawImage(imageToDraw,0, 0, getWidth(), getHeight(), null);
        g2D.dispose();
    }

    public void setOffset(int xOffset, int yOffset) {
        this.xOffset = xOffset;
        this.yOffset = yOffset;
    }

    public void startSelect(ScreenSelectFilter filter, ScreenSelectProcessor proc) {
        this.selFil = filter;
        this.selProc = proc;
        selectMode = true;
        this.prevViewed = game.getViewerEntity();
    }

    public void stopSelection() {
        selectMode = false;
        hTile = null;
        game.setViewerEntity(prevViewed);
        this.prevViewed = null;
    }
    
    private void setSelection(Point selection) {
        stopSelection();
        selProc.processSelection(level, selection);
    }

    private Point getTileOf(Point point) {
        int x = (point.x + (xOffset * Game.SCALE)) / (level.TILE_SIZE * Game.SCALE);
        int y = (point.y + (yOffset * Game.SCALE)) / (level.TILE_SIZE * Game.SCALE);
        return new Point(x, y);
    }

    private void highlightTile(Point p) {
        hTile = p;
    }

    private void recreateImages() {
        if (screenWidth > 0 && screenHeight > 0) {
            bufferImage = new BufferedImage(screenWidth, screenHeight, BufferedImage.TYPE_INT_RGB);
            imageToDraw = new BufferedImage(screenWidth, screenHeight, BufferedImage.TYPE_INT_RGB);
        }
    }

    public static interface ScreenSelectFilter {

        boolean acceptPoint(Level level, Point p);

    }

    public static interface ScreenSelectProcessor {

        void processSelection(Level level, Point p);
        
    }

    private class ScreenListener extends MouseAdapter {

        @Override
        public void mousePressed(MouseEvent e) {
            Point loc = getTileOf(e.getPoint());
            if (selectMode) {
                if (selFil.acceptPoint(level, loc)) {
                    setSelection(loc);
                }
            }
            else {
                Entity ent = level.entityAt(loc);
                if (ent != null) {
                    if (ent.isViewable()) {
                        game.setViewerEntity(ent);
                    }
                }
            }
        }

        @Override
        public void mouseMoved(MouseEvent e) {
            if (selectMode) {
                Point loc = getTileOf(e.getPoint());
                if (selFil.acceptPoint(level, loc)) {
                    highlightTile(loc);
                    if (level.entityAt(loc).isViewable()) {
                        game.setViewerEntity(level.entityAt(loc));
                    }
                }
                else {
                    highlightTile(null);
                    if (prevViewed != null) {
                        game.setViewerEntity(prevViewed);
                    }
                }
            }
        }
        
    }

}
